/*
 * sessoins.h
 *
 *  Created on: Dec 15, 2013
 *      Author: killerg
 */

#ifndef SESSOINS_H_
#define SESSOINS_H_

#include "share/net_s.h"
#include "share/net_c.h"
#include "share/connection_mgr.h"
#include "share/util.h"

#include <algorithm>

template<typename MsgT, int MsgTNum>
struct cli_session_t {
    typedef void (*handle_t)(MsgT* msg);

    virtual ~cli_session_t(){}

    virtual bool init(
    		const std::string &addr_,
    		std::function<void()> on_connected_ = NULL,
    		std::function<void()> on_disconnected_ = NULL,
    		bool nodelay = true) {
        for (int i=0; i<MsgTNum; ++i)
            handles[i] = NULL;

        addr = addr_;
        on_connected = on_connected_;
        on_disconnected = on_disconnected_;

        if (!tran.init(NULL, NULL, nodelay)) {
            UTIL_LOG(E, "client_session_t::init tran init failed");
            return false;
        }

        return true;
    }
    virtual void destroy() {
        tran.destroy();
    }
    virtual void update() {
        if (!tran.is_connected() && !tran.is_connecting()) {
            if (--reconnect_interval <= 0) {
                tran.connect(addr.c_str(), false, on_connected, on_disconnected);
                reconnect_interval = 5;
            }
        }

        if (tran.is_connected()) {
            auto msg = tran.recv<MsgT>();
            while (msg != NULL) {
                auto handle = get_handle(msg->id());
                if (handle)
                    handle(msg);

                safe_del(msg);
                msg = tran.recv<MsgT>();
            }
        }

        tran.update();
    }

    bool reg(int id, handle_t handle) {
        if (id <= 0 || id > MsgTNum) {
            UTIL_LOG(E,
                    "cli_session_t::reg cmd:%d out range",
                    id);
            return false;
        }

        if (NULL != handles[id]) {
            UTIL_LOG(E,
                    "cli_session_t::reg cmd:%d already regged",
                    id);
            return false;
        }

        handles[id] = handle;

        return true;
    }

    handle_t get_handle(int id) {
        if (id < 0 || id >= MsgTNum)
            return NULL;

        return handles[id];
    }

    std::string addr;
    tran_c tran;
    handle_t handles[MsgTNum];
    std::function<void()> on_connected;
    std::function<void()> on_disconnected;
    int reconnect_interval = 0;
};

template<typename SessT, typename RecvMsgT, int RecvMsgTNum, typename SendMsgT, int SendMsgTNum>
struct svr_session_t {
    typedef void (*handle_t)(SessT* sess, RecvMsgT* msg);
    struct send_msg_stub_t {
        int num;
        size_t total_size;
        size_t min_size;
        size_t max_size;
    };

    struct recv_msg_stub_t {
        int num;
        size_t total_size;
        size_t min_size;
        size_t max_size;
        handle_t handle;
    };

    std::string ip;
    int port;

    bool init(const char* ip, int port,
            int clientnum,
            int msg_ps_ = -1,
            double idle_time_ = -1.0f,
            size_t max_msg_size = 0xffffffff,
            bool nodelay = true,
            int deal_per_tick = 40) {

        this->ip = ip;
        this->port = port;

        for (int i=0; i<SendMsgTNum; ++i) {
            send_stubs[i].num = 0;
            send_stubs[i].total_size = 0;
            send_stubs[i].min_size = 999999;
            send_stubs[i].max_size = 0;
        }

        for (int i=0; i<RecvMsgTNum; ++i) {
            recv_stubs[i].num = 0;
            recv_stubs[i].total_size = 0;
            recv_stubs[i].min_size = 999999;
            recv_stubs[i].max_size = 0;
            recv_stubs[i].handle = NULL;
        }

        idle_time = idle_time_;
        msg_ps = msg_ps_;
        conn_times = 0;

        bool ret = net.init(clientnum, NULL, max_msg_size, nodelay, deal_per_tick);
        if (!ret) {
            UTIL_LOG(E,"svr_session_t::init net init fail");
            return ret;
        }
        ret = net.start_accept(
                ip,
                port,
                [this](tran_s* tran){
                    connmgr.create_conn(tran);
                    ++conn_times;
                },
                [this](tran_s* tran){
                    connmgr.delete_conn(tran);
                }
                );
        if (!ret) {
            UTIL_LOG(E,
                    "svr_session_t::init net start_accept fail addr:%s port:%d",
                    ip, port);
            return ret;
        }

        return ret;
    }
    void destroy() {
        UTIL_LOG(I, "%s:%d recv msg static start ---------------------------------------", ip.c_str(), port);
        size_t total_recv_size = 0;
        int total_recv_num = 0;
        int most_recv_max_size = 0;
        int most_recv_min_size = 99999;
        for (int i=0; i<RecvMsgTNum; ++i) {
            total_recv_num += recv_stubs[i].num;
            total_recv_size += recv_stubs[i].total_size;
            if (recv_stubs[i].max_size > most_recv_max_size)
            	most_recv_max_size  = recv_stubs[i].max_size;
            if (recv_stubs[i].min_size < most_recv_min_size)
            	most_recv_min_size  = recv_stubs[i].min_size;

            UTIL_LOG(I,
                    "msgid:%-10d total_sz:%-10d num:%-10d min_sz:%-10d max_sz:%-10d avg_sz:%-10d",
                    i,
                    recv_stubs[i].total_size,
                    recv_stubs[i].num,
                    recv_stubs[i].min_size,
                    recv_stubs[i].max_size,
                    recv_stubs[i].num ? int(double(recv_stubs[i].total_size) / recv_stubs[i].num) : 0
                    );
        }
        UTIL_LOG(I,
                "conn_times:%-10d total_size:%-10d total_num:%-10d total_avg_sz:%-10d most_min_sz:%-10d most_max_sz:%-10d",
                    conn_times,
                    total_recv_size,
                    total_recv_num,
                    total_recv_num ? int(double(total_recv_size) / total_recv_num) : 0,
					most_recv_min_size,
					most_recv_max_size
                    );
        UTIL_LOG(I, "%s:%d recv msg static end   ---------------------------------------", ip.c_str(), port);

        UTIL_LOG(I, "%s:%d send msg static start ---------------------------------------", ip.c_str(), port);
        size_t total_send_size = 0;
        int total_send_num = 0;
        int most_send_max_size = 0;
        int most_send_min_size = 99999;
        for (int i=0; i<SendMsgTNum; ++i) {
            total_send_num += send_stubs[i].num;
            total_send_size += send_stubs[i].total_size;
            if (send_stubs[i].max_size > most_send_max_size)
            	most_send_max_size  = send_stubs[i].max_size;
            if (send_stubs[i].min_size < most_send_min_size)
            	most_send_min_size  = send_stubs[i].min_size;

            UTIL_LOG(I,
                    "msgid:%-10d total_sz:%-10d num:%-10d min_sz:%-10d max_sz:%-10d avg_sz:%-10d",
                    i,
                    send_stubs[i].total_size,
                    send_stubs[i].num,
                    send_stubs[i].min_size,
                    send_stubs[i].max_size,
                    send_stubs[i].num ? int(double(send_stubs[i].total_size) / send_stubs[i].num) : 0
                    );
        }
        UTIL_LOG(I,
                "conn_times:%-10d total_size:%-10d total_num:%-10d total_avg_sz:%-10d most_min_sz:%-10d most_max_sz:%-10d",
                    conn_times,
                    total_send_size,
                    total_send_num,
                    total_send_num ? int(double(total_send_size) / total_send_num) : 0,
					most_send_min_size,
					most_send_max_size
                    );
        UTIL_LOG(I, "%s:%d send msg static end   ---------------------------------------", ip.c_str(), port);

        net.stop_accept();
        net.destroy();
    }
    void update(double delta) {
        net.update();
        for (auto itr:connmgr.map_trans) {
            tran_s* tran = itr.first;
            size_t msg_size = 0;
            auto msg = tran->recv<RecvMsgT>(&msg_size);
            int deal_num = 0;
            while (msg != NULL) {
                auto stub = get_stub(msg->id());
                if (stub && stub->handle) {
                    stub->handle(itr.second, msg);
                    ++stub->num;
                    stub->total_size += msg_size;
                    if (msg_size < stub->min_size)
                    	stub->min_size = msg_size;
                    if (msg_size > stub->max_size)
                    	stub->max_size = msg_size;
                }

                safe_del(msg);

                ++deal_num;
                if (msg_ps > 0 && deal_num >= msg_ps)
                    break;

                msg = tran->recv<RecvMsgT>(&msg_size);
            }

            if (deal_num == msg_ps) {
                UTIL_LOG(T,
                        "svr_session_t::update sess:%d kicked msg_num:%d exceed",
                        itr.second->_sessid, deal_num);
                itr.first->disconnect();
            }

            if (idle_time >= 0.0f) {
                if (deal_num == 0) {
                    itr.second->_idle_timer -= delta;
                    if (itr.second->_idle_timer <= 0) {
                        UTIL_LOG(T,
                                "svr_session_t::update sess:%d kicked idle:%fs",
                                itr.second->_sessid, idle_time);
                        itr.first->disconnect();
                    }
                } else {
                    itr.second->_idle_timer = idle_time;
                }
            }

            itr.second->update();
        }
    }

    bool send_msg(SessT* sess, SendMsgT* &msg) {
        size_t send_size = 0;
        int msgid = msg->id();
        bool ok = sess->_trans->send(msg, &send_size);
        if (ok) {
            if (msgid >= 0 && msgid < SendMsgTNum) {
                ++send_stubs[msgid].num;
                send_stubs[msgid].total_size += send_size;
                if (send_size < send_stubs[msgid].min_size)
                	send_stubs[msgid].min_size = send_size;
                if (send_size > send_stubs[msgid].max_size)
                	send_stubs[msgid].max_size = send_size;
            }
        }
        return ok;
    }

    bool send_buffer(SessT* sess, int32_t msgid, const void* buffer, size_t buff_len) {
        bool ok = sess->_trans->send_buffer(buffer, buff_len);
        if (ok) {
            if (msgid >= 0 && msgid < SendMsgTNum) {
                ++send_stubs[msgid].num;
                send_stubs[msgid].total_size += buff_len;
                if (buff_len < send_stubs[msgid].min_size)
                    send_stubs[msgid].min_size = buff_len;
                if (buff_len > send_stubs[msgid].max_size)
                    send_stubs[msgid].max_size = buff_len;
            }
        }
        return ok;
    }

    void disconnect_all() {
        for (auto itr : connmgr.map_trans) {
            tran_s* tran = itr.first;
            tran->disconnect();
        }
    }

    bool reg(int id, handle_t handle) {
        if (id < 0 || id >= RecvMsgTNum) {
            UTIL_LOG(E, "svr_session_t::reg id:%d out of range", id);
            return false;
        }

        if (NULL != recv_stubs[id].handle) {
            UTIL_LOG(E, "svr_session_t::reg id:%d already exist", id);
            return false;
        }

        recv_stubs[id].handle = handle;

        return true;
    }


    recv_msg_stub_t* get_stub(int id) {
        if (id < 0 || id >= RecvMsgTNum)
            return NULL;

        return recv_stubs + id;
    }

    connection_mgr<tran_s, SessT> connmgr;
    net_s net;
    recv_msg_stub_t recv_stubs[RecvMsgTNum];
    send_msg_stub_t send_stubs[SendMsgTNum];
    int conn_times;
    int msg_ps;
    double idle_time;

};

#endif /* SESSOINS_H_ */
